# == Synopsis 
#   This is a Fortify FPR to Appscan EXD converter.
#
# == Examples
#   This command accepts a Fortify FPR file and outputs an Appscan EXD .
#   ruby fpr_exd source_file.fpr
#
# == Usage 
#   ruby fpr_exd [options] source_file.fpr
#   For help use: ruby fpr_exd -h
#
# == Options
#   -h, --help          Displays help message
#   -v, --version       Display the version, then exit
#
# == Author
#   Cupcakes.
#
# == Copyright
#   Copyright (c) 2009 Cupcakes. Licensed under the MIT License:
#   http://www.opensource.org/licenses/mit-license.php
# end
require 'rubygems'
require 'rdoc/usage'
require 'optparse'
require 'ostruct'
require 'date'
require 'hpricot'
require 'highline/import'
require 'rexml/document'
require 'zip/zipfilesystem'
require 'rubyscript2exe'
include REXML

# Command line handler app
class FprExd
  VERSION = '1.0.0'

  attr_reader :options

  def initialize
	  @arguments = ARGV

	  #set defaults
	  @options = OpenStruct.new
  end

  #parse options and run
  def run
	if parsed_options? && arguments_valid? 
		puts "Start at #{DateTime.now}\n\n" 
      
      	output_options if @options.verbose # [Optional]
            
      	process_arguments            
      	process_command
      

		fpr = FprExdCmd.new(ARGV[0])
		fpr.run

      	puts "\nFinished at #{DateTime.now}" 
    else
      output_usage
    end
  end

	def parsed_options?
      # Specify options
      opts = OptionParser.new 
      opts.on('-v', '--version')    { output_version ; exit 0 }
      opts.on('-h', '--help')       { output_help }
      # TO DO - add additional options
            
      opts.parse!(@arguments) rescue return false
      
      process_options
      true      
    end

    # Performs post-parse processing on options
    def process_options
      @options.verbose = false if @options.quiet
    end
    
    def output_options
      puts "Options:\n"
      @options.marshal_dump.each do |name, val|        
        puts "  #{name} = #{val}"
      end
    end

    # True if required arguments were provided
    def arguments_valid?
      false if @arguments.length != 1 
	  true if @arguments[0] =~/(.fpr)\z/i
    end
    
    # Setup the arguments
    def process_arguments
      # TO DO - place in local vars, etc
    end
    
    def output_help
      output_version
    end
    
    def output_usage
	  puts "Usage"
	  puts "-----"
	  puts "ruby fpr_exd [options] source_file.fpr"
  	  puts "For help use: ruby fpr_exd -h"
    end
    
    def output_version
      puts "#{File.basename(__FILE__)} version #{VERSION}"
    end
    
    def process_command
	  puts "processing #{@arguments[0]}"
    end
end


class FprExdCmd

  	VERSION = '1.0.0'
	OUTDIR = 'fpr_out'
	
	#accepts filename and predefine filters
	def initialize(filename,filter=['cs','dll'])
		@filename = filename
		@ext_filters = filter
	end

	#start processing
	def run
		cleanup
		unzip
		load_xml
		extension_filter
		prefix_input ="inetpub/wwwroot/"
		until prefix_input == '.'
			check_prefix(prefix_input)
			puts "Keyword [#{prefix_input}] was removed from the file list."
			prefix_input = ask("Enter new text to remove from the file list.(input '.' when done) "){|q| q}
		end
		choose do |menu|
			menu.prompt ="Please select an output format:"
			menu.choice(:exd){exd_output}
			menu.choice(:txt){txt_output}
		end
		cleanup
	end

	#outputs the file listing to exd format
	def exd_output
		host =ask('server hostname/ip?')
		port =ask('port?',Integer) do |q| 
			q.default="80"
			q.in =1..65535
		end

		doc = Document.new
		doc.add_element('requests')
		@files.each do |path|
		entry = Element.new('request')
		entry.attributes["method"] = "GET"
		entry.attributes["scheme"] = "http"
		entry.attributes["httpVersion"] = 'HTTP/1.1'
		entry.attributes["host"] = host
		entry.attributes["port"] = port.to_s
		entry.attributes["path"] = path
		entry.attributes["boundary"] = ""
		entry.attributes["pathQuerySeparator"] = "?"
		entry.attributes["japEncoding"] = "0"

		headers =[
			{'Accept'=>'image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*'},
			{'Referer'=>"http://#{host}:#{port}/"},
			{'Accept-Language'=>'en'},
			{'UA-CPU'=>'x86'},
			{'User-Agent'=>'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)'},
			{'Host'=>host},
			{'Connection'=>'Keep-Alive'}
		]

		headers.each do |x|
			header_entry = Element.new('header')
			header_entry.attributes["name"]= x.keys[0]
			header_entry.attributes["value"]=x.values[0]
			entry.add_element header_entry
		end

		doc.root.add_element entry
		end
		File.open("file_listing.exd","w"){|f|doc.write(f,2)}
		puts "Results saved to file_listing.exd"
	end

	#generates a simple txt listing
	def txt_output 
		File.open("file_listing.txt",'w') do |f|
			@files.each{|x| f.puts x}
		end
		puts "Results saved to file_listing.txt"
	end

	#removes prefixs like Inetpub/wwwroot/
	#checks if there is a / at the end, else, it will add one
	def check_prefix prefix_filter
		prefix_filter = prefix_filter+"/" unless prefix_filter.end_with?("/")
		prefix_filter = "#{prefix_filter}(.*)"
		prefix= Regexp.new(prefix_filter,true)
		@files.collect!{ |i|
			file =''
			matchdata = prefix.match(i)
			file = matchdata[1]  if(matchdata)
			file = i if (!matchdata)
			file
		}
		puts @files

	end

	#filters xml file based on @ext_filters
	def extension_filter
		@ext_filters.each do |filter|
			regex = Regexp.new("(.#{filter})\\z",true)
			temp_list,@files = @files,[]
			@files = temp_list.reject {|f| regex.match(f)}
		end
	end

	#load a xml file to object
	def load_xml
		xml = File.read("#{OUTDIR}/audit.fvdl")
		doc,@files = Hpricot::XML(xml),[]
		(doc/"SourceFiles//File").each do |p|
			@files << p.inner_text.gsub("\\","/")
		end
	end

	#clean up the folder
	def cleanup
		begin
			FileUtils.rm_r OUTDIR
		rescue
		end
	end

	#unzip the fpr file
	def unzip
		begin
		Zip::ZipFile.open(@filename) { |zf|
			zf.each{ |e|
				fpath = File.join(OUTDIR,e.name)
				FileUtils.mkdir_p(File.dirname(fpath))
				zf.extract(e,fpath)
			}
		}
		rescue
			puts "#{@filename} not found or invalid fpr format"
			exit
		end
	end

end
